package com.example.demo.level3;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import javax.script.*;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 学习线程安全相关
 * 运行本文件的程序时，请保证至少2个可用的逻辑处理器，否则无法验证多线程
 * <p>
 * 注意：jdk的是否线程安全都会在各自的实现类中注明，此处只展示在并行流、多线程中使用线程不安全的类会造成的结果
 *
 * @author FangXiangmin
 */
@Slf4j
public class LearnThreadSafe {

    /**
     * List的线程安全实现与线程不安全实现
     * <p>
     * 以下对象都在各自的注释中写着，如果不愿意死记硬背，请在使用一个陌生的类之前，确认它是否为线程安全
     * ArrayList: Note that this implementation is not synchronized
     * LinkedList: Note that this implementation is not synchronized.
     * Collections.synchronizedList: Returns a synchronized (thread-safe) list backed by the specified list.
     * CopyOnWriteArrayList: A thread-safe variant of {@link java.util.ArrayList} in which all mutative operations ({@code add}, {@code set}, and so on) are implemented by making a fresh copy of the underlying array.
     */
    @Test
    public void aboutList() {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            list.add(i);
        }
        System.out.println("原始数据量: " + list.size());

        // ArrayList 是线程不安全的类，使用并行流时，需要注意，不能在并行流中对ArrayList中的数据进行修改操作
        List<Integer> streamList = new ArrayList<>();
        list.parallelStream().forEach(streamList::add);
        System.out.println("使用 ArrayList");
        System.out.println("使用 ArrayList 并行流插入结果数量 :" + streamList.size());
        // 结果不但数量不对，还会有null的数据产生
        System.out.println("使用 ArrayList 并行流插入结果中，数据为null的结果数量 :" + streamList.stream().filter(Objects::isNull).count());

        // LinkedList 是线程不安全的类，使用并行流时，需要注意，不能在并行流中对LinkedList中的数据进行修改操作
        streamList = new LinkedList<>();
        list.parallelStream().forEach(streamList::add);
        System.out.println("使用 LinkedList");
        System.out.println("使用 LinkedList 并行流插入结果数量 :" + streamList.size());
        // 结果不但数量不对，还会有null的数据产生
        System.out.println("使用 LinkedList 并行流插入结果中，数据为null的结果数量 :" + streamList.stream().filter(Objects::isNull).count());

        // Collections.synchronizedList 线程安全，根据传入的List进行加固
        streamList = new ArrayList<>();
        streamList = Collections.synchronizedList(streamList);
        list.parallelStream().forEach(streamList::add);
        System.out.println("使用 Collections.synchronizedList");
        System.out.println("使用 Collections.synchronizedList 并行流插入结果数量 :" + streamList.size());
        System.out.println("使用 Collections.synchronizedList 并行流插入结果中，数据为null的结果数量 :" + streamList.stream().filter(Objects::isNull).count());

        // CopyOnWriteArrayList 线程安全，是ArrayList的线程安全版本
        streamList = new CopyOnWriteArrayList<>();
        list.parallelStream().forEach(streamList::add);
        System.out.println("使用 CopyOnWriteArrayList");
        System.out.println("使用 CopyOnWriteArrayList 并行流插入结果数量 :" + streamList.size());
        System.out.println("使用 CopyOnWriteArrayList 并行流插入结果中，数据为null的结果数量 :" + streamList.stream().filter(Objects::isNull).count());
    }

    /**
     * Map的线程安全实现与线程不安全实现
     */
    @Test
    public void aboutMap() {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            list.add(i);
        }
        System.out.println("原始数据量: " + list.size());

        // HashMap 是线程不安全的类，使用并行流时，需要注意，不能在并行流中对HashMap中的数据进行修改操作
        Map<Integer, Integer> map1 = new HashMap<>(4);
        list.parallelStream().forEach(x -> map1.put(x, x));
        System.out.println("使用HashMap");
        System.out.println("并行流插入结果数量 :" + map1.size());

        // ConcurrentHashMap 线程安全
        Map<Integer, Integer> map2 = new ConcurrentHashMap<>(4);
        list.parallelStream().forEach(x -> map2.put(x, x));
        System.out.println("使用ConcurrentHashMap");
        System.out.println("并行流插入结果数量 :" + map2.size());
    }

    /**
     * Set的线程安全实现与线程不安全实现
     */
    @Test
    public void aboutSet() {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            list.add(i);
        }
        System.out.println("原始数据量: " + list.size());

        // HashSet 是线程不安全的类，使用并行流时，需要注意，不能在并行流中对 HashSet 中的数据进行修改操作
        Set<Integer> set = new HashSet<>();
        list.parallelStream().forEach(set::add);
        System.out.println("使用 HashSet");
        System.out.println("使用 HashSet 并行流插入结果数量 :" + set.size());

//        // TreeSet 是线程不安全的类，使用并行流时，需要注意，不能在并行流中对 TreeSet 中的数据进行修改操作
//        set = new TreeSet<>();
//        list.parallelStream().forEach(set::add);
//        System.out.println("使用 TreeSet");
//        System.out.println("使用 TreeSet 并行流插入结果数量 :" + set.size());

        // Collections.synchronizedSet 线程安全，根据传入的Set进行加固
        set = new HashSet<>();
        set = Collections.synchronizedSet(set);
        list.parallelStream().forEach(set::add);
        System.out.println("使用 Collections.synchronizedList");
        System.out.println("使用 Collections.synchronizedList 并行流插入结果数量 :" + set.size());

        // ConcurrentHashMap.newKeySet 线程安全，由ConcurrentHashMap提供
        set = ConcurrentHashMap.newKeySet();
        list.parallelStream().forEach(set::add);
        System.out.println("使用 ConcurrentHashMap.newKeySet");
        System.out.println("使用 ConcurrentHashMap.newKeySet 并行流插入结果数量 :" + set.size());
    }

    /**
     * 时间格式转换的线程安全实现与线程不安全实现
     */
    @Test
    public void aboutDate() {
        List<Integer> list = new ArrayList<>();
        // 获取逻辑处理器数量，确保会进入线程不安全的代码逻辑
        int core = Runtime.getRuntime().availableProcessors();
        for (int i = 0; i < core * 2; i++) {
            list.add(i);
        }
        System.out.println("原始数据量: " + list.size());

        // SimpleDateFormat 是线程不安全的，使用时需要注意
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        Date rightTime = null;
        try {
            rightTime = sdf.parse("2020-10-20");
            System.out.println("单独转换成功" + rightTime.toString());
        } catch (ParseException e) {
            e.printStackTrace();
        }
        // 转换的结果，既有可能报错，也有可能是错误的结果
        Date finalRightTime = rightTime;
        System.out.println("直接在并行流中");
        list.parallelStream().forEach(i -> {
            try {
                Date parsed = sdf.parse("2020-10-20");
                System.out.println("并行转换" + (finalRightTime.compareTo(parsed) == 0 ? "成功" : "失败 转换结果错误" + parsed.toString()));
            } catch (Exception e) {
                System.out.println("并行转换失败" + e.getMessage());
            }
        });
        // 当在线程内部创建 SimpleDateFormat 时，不会报错
        System.out.println("当在线程内部创建 SimpleDateFormat 时，不会报错");
        list.parallelStream().forEach(i -> {
            try {
                SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd");
                Date parsed = sdf2.parse("2020-10-20");
                System.out.println("并行转换" + (finalRightTime.compareTo(parsed) == 0 ? "成功" : "失败 转换结果错误" + parsed.toString()));
            } catch (Exception e) {
                System.out.println("并行转换失败" + e.getMessage());
            }
        });

        // jdk1.8提供了线程安全的时间相关处理的类
        // 如果使用LocalDate或者LocalDateTime，代码可以更简洁
        System.out.println("jdk1.8提供了线程安全的时间相关处理的类");
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        list.parallelStream().forEach(i -> {
            try {
                Date parsed = Date.from(LocalDate.parse("2020-10-20", dateTimeFormatter).atStartOfDay(ZoneId.systemDefault()).toInstant());
                System.out.println("并行转换" + (finalRightTime.compareTo(parsed) == 0 ? "成功" : "失败 转换结果错误" + parsed.toString()));
            } catch (Exception e) {
                System.out.println("并行转换失败" + e.getMessage());
            }
        });
    }


    private final ScriptEngineManager manager = new ScriptEngineManager();
    private final ScriptEngine engineTmp = manager.getEngineByName("js");

    @Test
    public void aboutScript() {

//        Bindings bindings = engineTmp.createBindings();
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            list.add(i);
        }
        System.out.println("原始数据量: " + list.size());

        list.parallelStream().forEach(i -> {
            try {
                Bindings bindings = engineTmp.createBindings();
//                Bindings bindings = new SimpleBindings();
                bindings.put("单价", i);
                bindings.put("面积", i);
                Double result = (Double) engineTmp.eval("单价*面积", bindings);
                double shouldBe = (double) i * i;
                if (shouldBe != result) {
                    System.out.println("error");
                }
            } catch (ScriptException e) {
                log.error(e.getMessage(), e);
            }
        });

        list.parallelStream().forEach(i -> {
            try {
                engineTmp.put("单价", i);
                engineTmp.put("面积", i);
                Double result = (Double) engineTmp.eval("单价*面积");
                double shouldBe = (double) i * i;
                if (shouldBe != result) {
                    System.out.println("error 2");
                }
            } catch (ScriptException e) {
                log.error(e.getMessage(), e);
            }
        });
    }

}
